package com.ubird.astar.core;

import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

public class AStarPathFinder {
	private Map<Thread, FinderContext> contexts;

	/** 障碍物 */
	public static final int STATE_BARRIER = 2;

	public AStarPathFinder(int xGridNum, int yGridNum) {
		contexts = new WeakHashMap<Thread, FinderContext>();
	}

	public synchronized FinderContext getContext() {
		FinderContext ctx = contexts.get(Thread.currentThread());
		if (ctx == null) {
			ctx = new FinderContext();
			contexts.put(Thread.currentThread(), ctx);
		}

		return ctx;
	}

	private TiledMap getMap() {
		return getContext().getMap();
	}

	public int[][] getAStarData() {
		return getMap().getData();
	}

	public List<AStarNode> getOpenList() {
		return getContext().getOpenList();
	}

	/**
	 * 搜索算法
	 */
	public List<AStarNode> find(TiledMap map, AStarNode source, AStarNode target) {
		FinderContext ctx = init(map, source, target);
		AStarNode current = null;
		while (!ctx.isEmpty() && !ctx.isAchieved()) {
			current = ctx.getMinFNode();
			if (isAchieve(current, target)) { // 是否已经完成寻路
				ctx.achieved();
				buildPath(ctx, current);
			} else {
				ctx.addToClosedList(current);
				for (AStarNode neighbor : getNeighbor(current)) {
					if (neighbor == null // 没有相邻节点
							|| ctx.isCloseMapContains(neighbor) // 已经在关闭列表
							|| isCanNotGo(current, neighbor)) /* 不能通过 */{
						continue;
					} else {
						ctx.touchNeighbor(neighbor, current, target);
					}
				}
			}
		}

		return ctx.getPath();
	}

	/**
	 * 判断从from结点到to结点是否不可行
	 * 
	 * @param from
	 * @param to
	 * @return
	 */
	private boolean isCanNotGo(AStarNode from, AStarNode to) {
		TiledMap tiledMap = getMap();
		if (isBarrier(to)) { /* 如果这一格已经是障碍物，那么不能走 */
			return true;
		} else { /* 如果他旁边 */
			int offsetX = from.getX() - to.getX();
			int offsetY = from.getY() - to.getY();
			if (Math.abs(offsetX) == 1 && Math.abs(offsetY) == 1) { // 只有在走斜线的时候才要继续判断
				if ((offsetX == 1
						&& offsetY == -1
						&& (isValidX(from.getX() - 1)
								&& tiledMap.isBarrier(from.getX() - 1,
										from.getY()) || isValidY(from.getY() + 1)
								&& tiledMap.isBarrier(from.getX(), from.getY() + 1)) || (offsetX == 1
						&& offsetY == 1
						&& (isValidY(from.getY() - 1)
								&& tiledMap.isBarrier(from.getX(), from.getY() - 1) || isValidX(from
								.getX() - 1)
								&& tiledMap.isBarrier(from.getX() - 1, from.getY()))
						|| (offsetX == -1 && offsetY == 1 && (isValidX(from.getX() + 1)
								&& tiledMap.isBarrier(from.getX() + 1, from.getY()) || isValidY(from.getY() - 1)
								&& tiledMap.isBarrier(from.getX(), from.getY() - 1))) || (offsetX == -1
						&& offsetY == -1 && (isValidX(from.getX() + 1)
						&& tiledMap.isBarrier(from.getX() + 1, from.getY()) || isValidY(from.getY() + 1)
						&& tiledMap.isBarrier(from.getX(), from.getY() + 1))))))
					return true;
			}
		}
		return false;
	}

	private boolean isValidX(int x) {
		return x >= 0 && x < getMap().getHorizontalTilesNum();
	}

	private boolean isValidY(int y) {
		return y >= 0 && y < getMap().getVerticalTilesNum();
	}

	private boolean isBarrier(AStarNode node) {
		return getMap().isBarrier(node.getX(), node.getY());
	}

	private List<AStarNode> buildPath(FinderContext ctx, AStarNode current) {
		if (current != null) {
			do {
				ctx.getPath().add(0, current);
				current = current.getFather();
			} while (current != null);
		}
		return ctx.getPath();
	}

	private AStarNode[] getNeighbor(AStarNode current) {
		TiledMap tiledMap = getMap();
		AStarNode[] neighbors = new AStarNode[9];
		for (int i = 0; i < neighbors.length; i++) {
			int x = current.getX() + i / 3 - 1;
			int y = current.getY() + i % 3 - 1;
			if (x < 0 || y < 0 || x >= tiledMap.getHorizontalTilesNum()
					|| y >= tiledMap.getVerticalTilesNum()
					|| (x == current.getX() && y == current.getY()))
				continue;
			neighbors[i] = new AStarNode(x, y);
		}
		return neighbors;
	}

	/**
	 * 比较指定结点是否是目标结点
	 * 
	 * @param current
	 * @return
	 */
	private boolean isAchieve(AStarNode current, AStarNode target) {
		return current.equals(target);
	}

	/**
	 * 初始化数据 开启列表和关闭列表 将源结点加入到开启列表中
	 */
	private FinderContext init(TiledMap map, AStarNode source, AStarNode target) {
		source.init(target);
		FinderContext ctx = getContext();
		ctx.reset(map, source, target);
		return ctx;
	}
}